#include "robot_mkdata.h"
#include "EPosixClientSocket.h"
#include "EPosixClientSocketPlatform.h"
#include "def.h"
#include <stdio.h>
#include <sstream>//stringstream
#include "log.h"
#include "pnl.h"
#include "guard.h"

using namespace std;

const int PING_DEADLINE = 2; // seconds
const int SLEEP_BETWEEN_PINGS = 30; // seconds
extern cmutex criticalvrlock_;

string getField(TickType field);

robot_mkdata::robot_mkdata():m_pClient(new EPosixClientSocket(this)),closeprice(0),openprice(0),opencloseadjusted(false)
{
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}

robot_mkdata::~robot_mkdata() { }

bool robot_mkdata::connect(const char *host, unsigned int port, int clientId) {
  TTPrint( "Connecting to %s:%d clientId:%d\n", !( host && *host) ? "127.0.0.1" : host, port, clientId);
  bool bRes = m_pClient->eConnect( host, port, clientId);
  if (bRes) {
    TTPrint( "Connected to %s:%d clientId:%d\n", !( host && *host) ? "127.0.0.1" : host, port, clientId);
    _state = MK_CONNECT;
  } else {
    TTPrint( "Cannot connect to %s:%d clientId:%d\n", !( host && *host) ? "127.0.0.1" : host, port, clientId);
  }
  return bRes;
}

void robot_mkdata::disconnect() const {
  m_pClient->eDisconnect();
  TTPrint ( "Disconnected\n");
}

bool robot_mkdata::isConnected() const {
  return m_pClient->isConnected();
}

void robot_mkdata::setsymbol(const string& s){
  pair<string,string> exchangecurrency=GetExchangeCurrency(s);
  contract.symbol = s;
  contract.secType = "STK";
  contract.exchange = exchangecurrency.first;
  contract.currency = exchangecurrency.second;
}

void robot_mkdata::processMessages(){
  fd_set readSet, writeSet, errorSet;
  struct timeval tval;
  tval.tv_usec = 0;
  tval.tv_sec = 0;

  switch (_state) {
    case MK_REQCONTRACT:
      reqContractDetails();
      break;
    case MK_REQCONTRACT_ACK:
      //contractDetails();
      break;
      /*case MK_ACCOUNT:
        reqAccountUpdates();
        break;
        case MK_ACCOUNTACK:
      //reqAccountUpdates();
      break;*/
    case MK_REQREALTIMEDATA:
      ReqMkData();
      break;
    case MK_REQREALTIMEDATAACK:
      break;
  }

  if( m_pClient->fd() >= 0 ) {
    FD_ZERO( &readSet);
    errorSet = writeSet = readSet;
    FD_SET( m_pClient->fd(), &readSet);
    if( !m_pClient->isOutBufferEmpty()){
      FD_SET( m_pClient->fd(), &writeSet);
    }
    FD_CLR( m_pClient->fd(), &errorSet);
    int ret = select( m_pClient->fd() + 1, &readSet, &writeSet, &errorSet, &tval);

    if( ret == 0) {return;}//// timeout
    if( ret < 0) {disconnect();return;}// error
    if( m_pClient->fd() < 0){return;}
    if( FD_ISSET( m_pClient->fd(), &errorSet)) {
      m_pClient->onError();
    }// error on socket
    if( m_pClient->fd() < 0){return;}
    if( FD_ISSET( m_pClient->fd(), &writeSet)) {
      m_pClient->onSend();
    }// socket is ready for writing
    if( m_pClient->fd() < 0){return;}
    if( FD_ISSET( m_pClient->fd(), &readSet)) {
      m_pClient->onReceive();
    }// socket is ready for reading
  }
}

void robot_mkdata::updateAccountValue(const string &key, const string &val, const string &currency, const string &accountName){}

void robot_mkdata::updatePortfolio(const Contract& c, int _position,
                              double marketPrice, double marketValue, 
                              double averageCost, double unrealizedPNL,
                              double realizedPNL, const IBString& accountName)
{
  if (c.symbol==contract.symbol){
    if (ismaster){
      pdiff->masterpos = _position;
    }else{
      pdiff->slavepos = _position;
    }
    static char str[512]={};
    sprintf(str,"conId=%ld,symbol=%s,secType=%s,expiry=%s,strike=%f,right=%s,multiplier=%s,primaryExchange=%s"
        ",currency=%s,localSymbol=%s,position=%d,mktPrice=%f,mktValue=%f,avgCost=%f,unrealizedPNL=%f,realizedPNL=%f,account=%s",
        c.conId, c.symbol.c_str(), c.secType.c_str(), c.expiry.c_str(),
        c.strike, c.right.c_str(), c.multiplier.c_str(), c.primaryExchange.c_str(),
        c.currency.c_str(), c.localSymbol.c_str(),
        _position, marketPrice, marketValue, averageCost, unrealizedPNL, realizedPNL, accountName.c_str());
    TTPrint("[%s]%s\n",__FUNCTION__,str);
  }
}

void robot_mkdata::reqAccountUpdates(bool subscribe){
  m_pClient->reqAccountUpdates(subscribe, account);
}

void robot_mkdata::reqContractDetails(){
  m_pClient->reqContractDetails(0,contract);
  if(_state<MK_REQCONTRACT_ACK){
    _state = MK_REQCONTRACT_ACK;
  }
}

void robot_mkdata::contractDetails(int reqId, const ContractDetails &contractDetails){
  contract.conId = contractDetails.summary.conId;
  if(_state<MK_REQREALTIMEDATA){
    _state = MK_REQREALTIMEDATA;
  }
}

void robot_mkdata::reqCurrentTime() {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}

void robot_mkdata::ReqMkData() {
  string gt="100,101,104,105,106,107,165,221,225,233,236,258,293,294,295,318";
  m_pClient->reqMktData( contract.conId, contract, gt, false);
  _state = MK_REQREALTIMEDATAACK;
}

void robot_mkdata::CancelMkData() {
  //TTPrint("Cancelling marketdata %d\n", mid);
  m_pClient->cancelMktData(tickerId);
}

//Error Code: https://www.interactivebrokers.com/en/software/api/apiguide/tables/api_message_codes.htm
void robot_mkdata::error(const int id, const int errorCode, const IBString errorString){
  //TTPrint( "[%s]Error id=%d, errorCode=%d, msg=%s\n", __PRETTY_FUNCTION__ ,id, errorCode, errorString.c_str());
  if( id == -1 && errorCode == 1100) // if "Connectivity between IB and TWS has been lost"
    disconnect();
  if (errorCode==103){
    cancelOrder(id);
  }
}

void robot_mkdata::tickPrice(TickerId _tickerId, TickType field, double price, int canAutoExecute) {
  tickerId = _tickerId;
  bool printinfo = false;
  stringstream str;

  if(field == BID || field == ASK){
    if (field == BID){
      if (ismaster){
        pdiff->master.bid = price;
      }else{
        pdiff->slave.bid = price;
      }
      str << "[" << contract.symbol << "]" << "BID=" << price << endl;
      TTPrint("%s",str.str().c_str());
      pdiff->u_limit(ismaster, BID, spread_band);
    }else if (field == ASK){
      if (ismaster){
        pdiff->master.ask = price;
      }else{
        pdiff->slave.ask = price;
      }
      str << "[" << contract.symbol << "]" << "ASK=" << price << endl;
      TTPrint("%s",str.str().c_str());
      pdiff->u_limit(ismaster, ASK, spread_band);
    }
  } else if(field==LAST) {
    PPP& p_=ibaccount::R().s2pp[contract.symbol];
    str << "[" << contract.symbol << "]" << "Lastprice=" << price;
    if (p_.second!=0){
      double realtime_unrealized_pnl=0, realtime_unrealized_return=0;
      realtime_unrealized_pnl = (price-p_.first)*p_.second;
      realtime_unrealized_return = realtime_unrealized_pnl/abs(p_.second*p_.first);
      str <<",Myprice="<<p_.first<< ",position="<< p_.second << ",unrealized_pnl="<<realtime_unrealized_pnl;
      str <<",unrealized_return="<<realtime_unrealized_return*100 <<"%";
    }
    str << endl;
    TTPrint("%s",str.str().c_str());
  }else if(field==CLOSE && !opencloseadjusted){
    TTPrint("[%s]%s=%f\n",contract.symbol.c_str(),getField(field).c_str(),price);
    closeprice = price;
  }else if(field==OPEN && !opencloseadjusted){
    TTPrint("[%s]%s=%f\n",contract.symbol.c_str(),getField(field).c_str(),price);
    openprice = price;
    if (closeprice!=0){
      opencloseadjusted=true;
      /*
      cguard g(lock_);//I can do this because this only happen when launching the program
      double tmp = ismaster? (openprice-closeprice) : -(openprice-closeprice);
      spread_band->s_threshold += tmp;
      spread_band->l_threshold += tmp;
      spread_band->liq_upper   += tmp;
      spread_band->liq_lower   += tmp;
      TTPrint("[%s]adjust spread by %f\n",__FUNCTION__,tmp);
      */
    }
  }
}

void robot_mkdata::tickSize(TickerId tickerId, TickType field, int size){
  //stringstream str;str<<"["<<symbol<<"]"<<"id="<<tickerId<<","<<getField(field)<<"="<<size<<endl;
  //TTPrint("%s",str.str().c_str());
  if (field == BID_SIZE){
    if (ismaster){
      pdiff->master.bidsize = size;
    }else{
      pdiff->slave.bidsize = size;
    }
  } else if (field == ASK_SIZE){
    if (ismaster){
      pdiff->master.asksize = size;
    }else{
      pdiff->slave.asksize = size;
    }
  }
}

void robot_mkdata::cancelOrder(int oid){ }
void robot_mkdata::tickOptionComputation( TickerId tickerId, TickType field, double impliedVol, double delta, double optPrice, double pvDividend, double gamma, double vega, double theta, double undPrice) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::tickGeneric(TickerId tickerId, TickType field, double value) {
  
}
void robot_mkdata::tickString(TickerId tickerId, TickType field, const IBString& value) {
  if(field==OPEN || field==CLOSE){
    TTPrint("[%s]%s\n",__FUNCTION__,value.c_str());
  }
}
void robot_mkdata::tickEFP(TickerId tickerId, TickType field, double basisPoints, const IBString& formattedBasisPoints, double totalDividends, int holdDays, const IBString& futureExpiry, double dividendImpact, double dividendsToExpiry) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::openOrder(OrderId orderId, const Contract&, const Order&, const OrderState&){}
void robot_mkdata::openOrderEnd() {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::winError( const IBString &str, int lastError) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::connectionClosed() {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::updateAccountTime(const IBString& timeStamp) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::accountDownloadEnd(const IBString& accountName) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::bondContractDetails( int reqId, const ContractDetails& contractDetails) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::contractDetailsEnd( int reqId) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::execDetails( int reqId, const Contract& contract, const Execution& execution) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::execDetailsEnd( int reqId) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::updateMktDepth(TickerId id, int position, int operation, int side, double price, int size) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::updateMktDepthL2(TickerId id, int position, IBString marketMaker, int operation, int side, double price, int size) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::updateNewsBulletin(int msgId, int msgType, const IBString& newsMessage, const IBString& originExch) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::managedAccounts( const IBString& accountsList) {
  TTPrint("[clientid=%d]the managed account is:[%s]\n", m_pClient->clientId(), accountsList.c_str());
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::receiveFA(faDataType pFaDataType, const IBString& cxml) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::historicalData(TickerId reqId, const IBString& date, double open, double high, double low, double close, int volume, int barCount, double WAP, int hasGaps) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::scannerParameters(const IBString &xml) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::scannerData(int reqId, int rank, const ContractDetails &contractDetails, const IBString &distance, const IBString &benchmark, const IBString &projection, const IBString &legsStr) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::scannerDataEnd(int reqId) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::realtimeBar(TickerId reqId, long time, double open, double high, double low, double close, long volume, double wap, int count) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::fundamentalData(TickerId reqId, const IBString& data) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::deltaNeutralValidation(int reqId, const UnderComp& underComp) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::tickSnapshotEnd(int reqId) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::marketDataType(TickerId reqId, int marketDataType) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::commissionReport( const CommissionReport& commissionReport) {
  TTPrint("[%s]\n",__FUNCTION__);
}
void robot_mkdata::position( const IBString& account, const Contract& contract, int position) {
  TTPrint("[%s]\n",__FUNCTION__);
}
void robot_mkdata::positionEnd() {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::accountSummary( int reqId, const IBString& account, const IBString& tag, const IBString& value, const IBString& curency) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::accountSummaryEnd( int reqId) {
  //TTPrint("[%s]\n",__PRETTY_FUNCTION__);
}
void robot_mkdata::orderStatus(OrderId orderId, const IBString &status, int filled, int remaining,double avgFillPrice, int permId, int parentId, double lastFillPrice, int clientId, const IBString& whyHeld){}

void robot_mkdata::nextValidId(OrderId orderId) { }

void robot_mkdata::currentTime(long time) { }

string getField( TickType field) {
  switch( field){
    case BID_SIZE:                      return "bidSize";
    case BID:                           return "bidPrice";
    case ASK:                           return "askPrice";
    case ASK_SIZE:                      return "askSize";
    case LAST:                          return "lastPrice";
    case LAST_SIZE:                     return "lastSize";
    case HIGH:                          return "high";
    case LOW:                           return "low";
    case VOLUME:                        return "volume";
    case CLOSE:                         return "close";
    case BID_OPTION_COMPUTATION:        return "bidOptComp";
    case ASK_OPTION_COMPUTATION:        return "askOptComp";
    case LAST_OPTION_COMPUTATION:       return "lastOptComp";
    case MODEL_OPTION:                  return "optionModel";
    case OPEN:                          return "open";
    case LOW_13_WEEK:                   return "13WeekLow";
    case HIGH_13_WEEK:                  return "13WeekHigh";
    case LOW_26_WEEK:                   return "26WeekLow";
    case HIGH_26_WEEK:                  return "26WeekHigh";
    case LOW_52_WEEK:                   return "52WeekLow";
    case HIGH_52_WEEK:                  return "52WeekHigh";
    case AVG_VOLUME:                    return "AvgVolume";
    case OPEN_INTEREST:                 return "OpenInterest";
    case OPTION_HISTORICAL_VOL:         return "OptionHistoricalVolatility";
    case OPTION_IMPLIED_VOL:            return "OptionImpliedVolatility";
    case OPTION_BID_EXCH:               return "OptionBidExchStr";
    case OPTION_ASK_EXCH:               return "OptionAskExchStr";
    case OPTION_CALL_OPEN_INTEREST:     return "OptionCallOpenInterest";
    case OPTION_PUT_OPEN_INTEREST:      return "OptionPutOpenInterest";
    case OPTION_CALL_VOLUME:            return "OptionCallVolume";
    case OPTION_PUT_VOLUME:             return "OptionPutVolume";
    case INDEX_FUTURE_PREMIUM:          return "IndexFuturePremium";
    case BID_EXCH:                      return "bidExch";
    case ASK_EXCH:                      return "askExch";
    case AUCTION_VOLUME:                return "auctionVolume";
    case AUCTION_PRICE:                 return "auctionPrice";
    case AUCTION_IMBALANCE:             return "auctionImbalance";
    case MARK_PRICE:                    return "markPrice";
    case BID_EFP_COMPUTATION:           return "bidEFP";
    case ASK_EFP_COMPUTATION:           return "askEFP";
    case LAST_EFP_COMPUTATION:          return "lastEFP";
    case OPEN_EFP_COMPUTATION:          return "openEFP";
    case HIGH_EFP_COMPUTATION:          return "highEFP";
    case LOW_EFP_COMPUTATION:           return "lowEFP";
    case CLOSE_EFP_COMPUTATION:         return "closeEFP";
    case LAST_TIMESTAMP:                return "lastTimestamp";
    case SHORTABLE:                     return "shortable";
    case FUNDAMENTAL_RATIOS:            return "fundamentals";
    case RT_VOLUME:                     return "RTVolume";
    case HALTED:                        return "halted";
    case BID_YIELD:                     return "bidYield";
    case ASK_YIELD:                     return "askYield";
    case LAST_YIELD:                    return "lastYield";
    case CUST_OPTION_COMPUTATION:       return "custOptComp";
    case TRADE_COUNT:                   return "trades";
    case TRADE_RATE:                    return "trades/min";
    case VOLUME_RATE:                   return "volume/min";
    case LAST_RTH_TRADE:                return "lastRTHTrade";
    default:                            return "unknown";
  }
}
